<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <style>
            html, body {
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
                height: 100%;
                margin: 0;
            }

            .container {
                display: flex;
                flex-direction: column;
                height: 100vh;
            }

            .left-column {
                display: flex;
                flex-direction: row;
                max-height: 40vh;
            }

            .code-box {
                flex: 1.5;
                height: 100%;
                background-color: #f5f5f5;
                padding: 16px;
                overflow: auto;
                border-right: 1px solid #ccc;
                word-wrap: break-word;
                border-bottom: none;
            }

            .error-box {
                flex: 1;
                height: 100%;
                background-color: #f5f5f5;
                padding: 16px;
                overflow: auto;
                color: red;
                box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);
            }
            .code-box-header {
                font-weight: bold;
                margin-bottom: 10px;
                padding-bottom: 5px;
                border-bottom: 1px solid #cccccc;
                color: #333;
                font-size: large;
            }

            .error-box-header {
                font-weight: bold;
                margin-bottom: 10px;
                padding-bottom: 5px;
                border-bottom: 1px solid #000000;
                color: #333;
                font-size: large;
            }

            .graph-box-header {
                font-weight: bold;
                margin-bottom: 10px;
                padding-bottom: 5px;
                border-bottom: 1px solid #000000;
                color: #333;
                font-size: large;
            }

            .code-box pre {
                margin: 0;
                padding: 0;
            }

            .code-box code {
                font-family: monospace;
            }

            .graph-view {
                height: 60vh;
                overflow: hidden;
                padding: 16px;
                background-color: #ffffff;
            }
            #graph {
                width: 100%;
                height: 100%;
            }
            .node rect {
                stroke: #333;
                fill: #fff;
            }
            text.node-label {
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
                cursor: default;
                user-select: none;
            }
            text.type-indicator {
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
                cursor: default;
                user-select: none;
            }
            text.collapse-icon {
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
                font-size: 14px;
                cursor: default;
                user-select: none;
            }
            .edge {
                stroke: #797979;
                fill: none;
            }
            .node rect.input { fill: #FF9AFF; }
            .node rect.constant { fill: rgb(235 235 235); }

            .node rect.output { fill: #74e071; }
            .node rect.failed { fill: rgb(224 15 15); }
            .node rect.module { fill: #8BDEDB; }
            .node rect.operation { fill: #FEBC5A; }
            .edgeLabelGroup {
                pointer-events: none;
            }
            #arrowhead path {
                fill: #333333;;
            }
            .node rect,
            .node ellipse {
                transition: fill 0.3s ease;
            }

            .node .type-indicator {
                font-size: 5px;
                fill: rgba(0, 0, 0, 1.0);  /* Dark grey, semi-transparent */
                pointer-events: none;
            }

            .node.error rect {
                stroke: black;
                stroke-width: 1px;
                /* stroke-dasharray: 3, 3; */
                rx: 5px;
                ry: 5px;
            }

            .boundary-box.error > rect:first-of-type {
                fill: red;
                fill-opacity: 0.2;
            }

            #graph-container {
                position: relative;
            }
            .popup {
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
                position: absolute;
                background: white;
                border: 1px solid #ccc;
                width: 500px;
                resize: horizontal;
                overflow: auto;
                max-height: 70%;
                box-shadow: 0 2px 10px rgba(0,0,0,0.2);
                display: none;
                display: flex;
                flex-direction: column;
            }

            .popup-header {
                position: sticky;
                top: 0;
                background: white;
                padding: 5px 10px;
                border-bottom: 1px solid #ccc;
                display: flex;
                justify-content: space-between;
                align-items: center;
                z-index: 1;
            }

            .popup h3 {
                margin: 0;
                font-size: 16px;
            }

            .popup-content {
                padding: 10px;
                padding-bottom: 10px;
                overflow-y: auto;
                overflow-x: auto;
                flex-grow: 1;
            }

            .popup pre {
                white-space: pre;
                overflow-x: auto;
            }

            .popup-close {
                cursor: pointer;
                font-size: 24px;
                font-weight: bold;
                line-height: 1;
            }

            #popup-title {
                font-size: 16px;
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
            }

            #graph-container {
                position: relative;
            }

            pre {
                margin-bottom: 0;
            }

            .boundary-box rect {
                pointer-events: none;
            }
            .boundary-box text {
                pointer-events: none;
                user-select: none;
            }

            .boundary-box rect {
                pointer-events: none;
            }
            .boundary-box text {
                pointer-events: none;
                user-select: none;
                font-weight: bold;
            }
            .edge-label text {
                pointer-events: none;
                user-select: none;
                font-size: 4px;
                font-family: "'Roboto', sans-serif";
            }
            
            .collapse-button:hover {
                fill: #eee;
                cursor: pointer;
            }
            .collapse-icon {
                pointer-events: none;
                user-select: none;
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
                fill: rgb(51, 51, 51);
            }

            /* Edge hover styles */`
            .edge-group path {
               pointer-events: stroke;
            }

            .edge-label {
                pointer-events: all;
            }

            @keyframes containerHighlight {
                0% { 
                    filter: drop-shadow(0 0 0px rgba(255, 215, 0, 0));
                }
                25% { 
                    filter: drop-shadow(0 0 15px rgba(255, 215, 0, 0.8));
                }
                50% { 
                    filter: drop-shadow(0 0 0px rgba(255, 215, 0, 0));
                }
                75% { 
                    filter: drop-shadow(0 0 15px rgba(255, 215, 0, 0.8));
                }
                100% { 
                    filter: drop-shadow(0 0 0px rgba(255, 215, 0, 0));
                }
            }

            @keyframes containerStrokeHighlight {
                0% { 
                    stroke-width: 1px;
                }
                25% { 
                    stroke-width: 3px;
                }
                50% { 
                    stroke-width: 1px;
                }
                75% { 
                    stroke-width: 3px;
                }
                100% { 
                    stroke-width: 1px;
                }
            }

            .highlight-cluster > rect:first-of-type {
                animation: 
                    containerStrokeHighlight 2.0s ease-in-out forwards;
            }

            .highlight-node rect {
                animation: 
                    containerStrokeHighlight 2.0s ease-in-out forwards;
            }

            text.node-label {
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
                font-size: 0.8rem;  /* Base size using rem units for accessibility */
                cursor: default;
                user-select: none;
            }

            .node.constant .node-label {
                font-size: 0.7rem;
            }

            .top-bar {
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                color: white;
                padding: 12px 20px;
                display: flex;
                justify-content: space-between;
                align-items: center;
                box-shadow: 0 2px 8px rgba(0,0,0,0.1);
                min-height: 25px;
            }

            .repo-title {
                font-size: 24px;
                font-weight: bold;
                margin: 0;
                font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
            }

            /* GitHub button iframe styling */
            .github-star-button {
                border-radius: 6px;
                overflow: hidden;
            }

            .json-viewer {
                height: 60%;
                width: 100%;
            }

            .popup-content summary {
                cursor: pointer;
                margin: 5px;
                user-select: none;
            }

            .node.input rect,
            .node.output rect,
            .node.constant rect {
                stroke-width: 1px;
                rx: 8px;
                ry: 8px;
                stroke-dasharray: none;
            }

        </style>
    </head>
    <body>
        <script>
            ${d3_source}
        </script>
        <script>
            ${viz_source}
        </script>
    
        <style>
            ${jsoneditor_css}
        </style>
    
        <script>
            ${jsoneditor_source}
        </script>
    
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/atom-one-dark.min.css">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/python.min.js"></script>
        <script>
        
(function() {
    document.addEventListener('DOMContentLoaded', (event) => {
        document.querySelectorAll('pre code').forEach((block) => {
            hljs.highlightBlock(block);
        });
    });


    const adj_list = ${adj_list_json};
    const module_info = ${module_info_json};
    const func_info = ${func_info_json};
    const parent_module_to_nodes = ${parent_module_to_nodes_json};
    const parent_module_to_depth = ${parent_module_to_depth_json};
    const graph_node_name_to_without_suffix = ${graph_node_name_to_without_suffix};
    const ancestor_map = ${ancestor_map};
    const max_module_expansion_depth = ${max_module_expansion_depth};
    const node_to_module_path = ${node_to_module_path};
    var collapsedContainers = new Set();
    const containerNodes = new Set();
    var adj_list_collapsed_nodes = {};
    let containerHierarchy = {};
    let vizInstance = null;
    let containerNodeCounts = {};

    const EXPAND_TARGET_COVERAGE = 0.8;
    const COLLAPSE_TARGET_COVERAGE = 0.2;
    let lastModifiedContainer = null; // Track the last modified container
    let lastOperation = null; // Track whether it was 'expand' or 'collapse'
    
    function waitForLibs(callback) {
        if (typeof d3 !== 'undefined' && typeof Viz !== 'undefined') {
            Viz.instance().then(viz => {
                vizInstance = viz;
                callback();
            });
        } else {
            setTimeout(function() { waitForLibs(callback) }, 400);
        }
    }

    function extractGraphData(graphData) {
        const result = {
            nodes: {},
            edges: [],
            clusters: {}
        };

        // Process nodes - they have _gvid property and may have a "pos" attribute
        for (const obj of graphData.objects) {
            // Skip cluster objects (which are subgraphs)
            if (obj.name && obj.name.startsWith("cluster_")) {
                result.clusters[obj.name.replace("cluster_", "")] = {
                    bb: obj.bb, // Bounding box "x1,y1,x2,y2"
                    label: obj.label,
                    nodes: obj.nodes ? obj.nodes.map(id => graphData.objects[id].name) : []
                };
                continue;
            }
        
            // This is a node
            if (obj.pos) {
            const [x, y] = obj.pos.split(",").map(Number);
                result.nodes[obj.name] = {
                id: obj._gvid,
                name: obj.name,
                position: { x, y },
                    width: parseFloat(obj.width) * 72, // Convert to pixels (72 points per inch)
                    height: parseFloat(obj.height) * 72,
                    label: obj.label === "\\N" ? obj.name : obj.label
                };
            }
        }

        // Process edges
        for (const edge of graphData.edges) {
            const sourceId = edge.tail;
            const targetId = edge.head;

            // Find node names by their _gvid
            const sourceName = Object.values(result.nodes).find(n => n.id === sourceId)?.name;
            const targetName = Object.values(result.nodes).find(n => n.id === targetId)?.name;

            if (!sourceName || !targetName) continue;

            // Extract path points from the pos attribute
            let pathPoints = [];
            if (edge.pos) {
                // IMPORTANT CHANGE: Correctly handle the "e," prefix
                const posStr = edge.pos;
                // Check if the string starts with "e,"
                if (posStr.startsWith("e,")) {
                    // Split the string and extract coordinates correctly
                    const parts = posStr.split(" ");
                    // Extract the endpoint (first coordinate after "e,")
                    const endPoint = parts[0].substring(2).split(",").map(Number);
                    // Add the remaining points
                    pathPoints = parts.slice(1).map(point => {
                        const [x, y] = point.split(',').map(Number);
                        return { x, y };
                    });

                    // Add the endpoint as the last point in the path
                    pathPoints.push({ x: endPoint[0], y: endPoint[1] });
                } else {
                    // Handle normal path without "e," prefix
                    pathPoints = posStr.split(' ').map(point => {
                        const [x, y] = point.split(',').map(Number);
                        return { x, y };
                    });
                }
            }
        
            let labelPos = null;
            if (edge.lp) {
                const [lx, ly] = edge.lp.split(',').map(Number);
                labelPos = { x: lx, y: ly };
            }
            result.edges.push({
                source: sourceName,
                target: targetName,
                path: pathPoints,
                edge_data_id: edge.edge_data_id,
                labelPos: labelPos
            });
        }

        return result;
    }

    function identifyContainersWithFailedNodes() {
        const failedNodes = Object.keys(adj_list).filter(node => adj_list[node].failed);
        
        const containersWithFailedNodes = new Set();
        
        failedNodes.forEach(failedNode => {
            let currentNode = failedNode;

            while (ancestor_map[currentNode]) {
                const container = ancestor_map[currentNode];
                containersWithFailedNodes.add(container);
                currentNode = container;
            }
        });
        
        return containersWithFailedNodes;
    }

    function processCollapsedGraph() {
        adj_list_collapsed_nodes = {};
        // Build container hierarchy
        containerHierarchy = {};
        for (const [node, parent] of Object.entries(ancestor_map)) {
            if (parent && !containerHierarchy[parent]) {
                containerHierarchy[parent] = [];
            }
            if (parent) {
                containerHierarchy[parent].push(node);
            }
        }

        // Helper function to get all descendants of a container
        function getAllDescendants(container) {
            let descendants = [];
            const children = containerHierarchy[container] || [];
            for (const child of children) {
                descendants.push(child);
                if (containerNodes.has(child)) {
                    descendants = descendants.concat(getAllDescendants(child));
                }
            }
            return descendants;
        }
        
        // First, add all collapsed containers as nodes
        for (const container of collapsedContainers) {
            adj_list_collapsed_nodes[container] = {
                node_type: "Module",
                edges: []
            };
        }
        
        for (const [node, data] of Object.entries(adj_list)) {
            let nodeIsInsideCollapsed = false;
            
            // Check if this node is inside a collapsed container
            for (const container of collapsedContainers) {
                const descendants = getAllDescendants(container);
                if (descendants.includes(node)) {
                    nodeIsInsideCollapsed = true;
                    break;
                }
            }
            
            // If node is not inside a collapsed container, add it to the processed list
            if (!nodeIsInsideCollapsed) {
                adj_list_collapsed_nodes[node] = {
                    node_type: data.node_type,
                    edges: []
                };
            }
        }

        function findVisibleCollapsedContainers() {
            const allDescendants = new Set();
            const visibleContainers = new Set();

            for (const container of collapsedContainers) {
                const descendants = getAllDescendants(container, containerHierarchy);
                for (const desc of descendants) {
                    allDescendants.add(desc);
                }
            }

            for (const container of collapsedContainers) {
                if (!allDescendants.has(container)) {
                    visibleContainers.add(container);
                }
            }

            return visibleContainers;
        }

        

        let visibleCollapsedContainers = findVisibleCollapsedContainers();
        
        // Process edges
        for (const [node, data] of Object.entries(adj_list)) {
            let seenEdgeDataIds = {};

            for (const edge of data.edges) {
                let sourceNode = node;
                let targetNode = edge.target;
                let skipEdge = false;

                for (const collapsedContainer of visibleCollapsedContainers) {
                    const descendants = getAllDescendants(collapsedContainer);

                    if (descendants.includes(node)) {
                        sourceNode = collapsedContainer;
                    }

                    if (descendants.includes(edge.target)) {
                        targetNode = collapsedContainer;
                    }

                    // Skip internal edges within collapsed containers
                    if (descendants.includes(node) && descendants.includes(edge.target)) {
                        skipEdge = true;
                    }
                }

                if (skipEdge || sourceNode === targetNode || !adj_list_collapsed_nodes[sourceNode]) {
                    continue;
                }

                // Create a consistent key for the source-target pair
                const pairKey = `${sourceNode}->${targetNode}`;
                const edgeId = edge.edge_data_id;

                if (edgeId) {
                    if (!seenEdgeDataIds[pairKey]) {
                        seenEdgeDataIds[pairKey] = new Set();
                    }

                    // Skip if this edge_data_id is already added between this pair
                    if (seenEdgeDataIds[pairKey].has(edgeId)) {
                        continue;
                    }

                    seenEdgeDataIds[pairKey].add(edgeId);
                }

                adj_list_collapsed_nodes[sourceNode].edges.push({
                    target: targetNode,
                    dims: edge.dims,
                    edge_data_id: edge.edge_data_id,
                });
            }
        }
    }

    function generateDotFromProcessedData() {
        let dotSource = 'digraph G {\n';
        dotSource += '  rankdir=BT;\n';
        dotSource += '  node [shape=box];\n';
        dotSource += '  ranksep=0.5;\n';
        dotSource += '  nodesep=0.4;\n';

        // Find top containers
        const topContainers = new Set();
        for (const container of containerNodes) {
            if (!ancestor_map[container] & !collapsedContainers.has(container)) {
                topContainers.add(container);
            }
        }

        // Process containers recursively
        function processContainer(containerName, level = 0) {
            const indent = ' '.repeat(level * 2);
            
            // Check if this container is collapsed (appears as a regular node)
            if (collapsedContainers.has(containerName)) {
                // For collapsed containers, create a normal node instead of a subgraph
                dotSource += `${indent}"${containerName}";\n`;
                return;
            }
            
            // Start subgraph for non-collapsed containers
            dotSource += `${indent}subgraph cluster_${containerName} {\n`;
            dotSource += `${indent}  label="${containerName}";\n`;
            dotSource += `${indent}  style=rounded;\n`;

            // Get all direct children
            const children = containerHierarchy[containerName] || [];

            // Process child containers first
            for (const child of children) {
                if (containerNodes.has(child)) {
                    processContainer(child, level + 1);
                }
            }

            // Add regular nodes that are direct children
            for (const child of children) {
                if (!containerNodes.has(child) && adj_list_collapsed_nodes[child]) {
                    const nodeData = adj_list_collapsed_nodes[child];
                    if (nodeData.node_type === "Constant") {
                        dotSource += `${indent}  "${child}" [width=0.7, height=0.3];\n`;
                    } else if (nodeData.node_type === "Input" || nodeData.node_type === "Output") {
                        dotSource += `${indent}  "${child}" [width=1.2, height=0.5];\n`;
                    } else {
                        dotSource += `${indent}  "${child}";\n`;
                    }
                }
            }

            dotSource += `${indent}}\n`;
        }

        // Process all top containers
        for (const container of topContainers) {
            processContainer(container);
        }

        // Add standalone nodes (not in any container)
        for (const [node, data] of Object.entries(adj_list_collapsed_nodes)) {
            if (!ancestor_map[node]) {
                if (data.node_type === "Constant") {
                    dotSource += `  "${node}" [width=0.7, height=0.3];\n`;
                } else if (data.node_type === "Input" || data.node_type === "Output") {
                    dotSource += `  "${node}" [width=1.2, height=0.5];\n`;
                } else {
                    dotSource += `  "${node}";\n`;
                }
            }
        }

        // Add all edges
        for (const [node, data] of Object.entries(adj_list_collapsed_nodes)) {
            for (const edge of data.edges) {
                let edgeLabelFontSize = "10";
                if (edge.edge_data_id) {
                    dotSource += `  "${node}" -> "${edge.target}"[label="${edge.dims}", fontsize="${edgeLabelFontSize}", edge_data_id="${edge.edge_data_id}" ];\n`;
                } else {
                    dotSource += `  "${node}" -> "${edge.target}"[label="${edge.dims}", fontsize="${edgeLabelFontSize}" ];\n`;
                }
            }
        }

        dotSource += '}';
        return dotSource;
    }

    // Updated generateDotSource function
    function generateDotSource() {
        processCollapsedGraph();
        const dotSource = generateDotFromProcessedData();
        return dotSource;
    }

    function calculateBoundingBox(nodeGroup, nodePositions) {
        let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
        nodeGroup.forEach(nodeName => {
            const node = nodePositions[nodeName];
            if (node) {
                minX = Math.min(minX, node.x - node.width / 2);
                minY = Math.min(minY, node.y - node.height / 2);
                maxX = Math.max(maxX, node.x + node.width / 2);
                maxY = Math.max(maxY, node.y + node.height / 2);
            }
        });
        return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
    }

    function initContainerNodes() {
        // Clear and rebuild the containerNodes set
        containerNodes.clear();
        for (const value of Object.values(ancestor_map)) {
            if (value !== null && value !== undefined) {
                containerNodes.add(value);
            }
        }
    }

    function calculateContainerNodeCounts() {
        containerNodeCounts = {};
        
        for (const container of containerNodes) {
            containerNodeCounts[container] = 0;
        }
        
        for (const [node, parent] of Object.entries(ancestor_map)) {
            if (parent) {
                containerNodeCounts[parent] = (containerNodeCounts[parent] || 0) + 1;
                
                let currentParent = ancestor_map[parent];
                while (currentParent && !collapsedContainers.has(parent)) {
                    containerNodeCounts[currentParent] = (containerNodeCounts[currentParent] || 0) + 1;
                    currentParent = ancestor_map[currentParent];
                }
            }
        }
    }

    function reloadGraph() {
        d3.select("#graph").html("");

        const dotSource = generateDotSource(adj_list, ancestor_map);

        graphData = vizInstance.renderJSON(dotSource)
        // Extract the graph data with positions from Graphviz
        const extractedGraphData = extractGraphData(graphData);

        
        // Now create the graph with the Graphviz layout data
        createGraph(extractedGraphData);

         
        // Needed to get the right vp coverage when containers are expanded
        calculateContainerNodeCounts();

        // renderGraphvizImage(dotSource);
    }
                
    function initializeCollapsedContainersFromGraph() {
        const containerDepths = new Map();
        
        function getContainerDepth(container) {
            if (containerDepths.has(container)) {
                return containerDepths.get(container);
            }
            
            let depth = 0;
            for (const [child, parent] of Object.entries(ancestor_map)) {
                if (child === container && parent) {
                    depth = getContainerDepth(parent) + 1;
                    break;
                }
            }
            
            containerDepths.set(container, depth);
            return depth;
        }
        
        for (const container of containerNodes) {
            const depth = getContainerDepth(container);
            if (depth >= max_module_expansion_depth) {
                collapsedContainers.add(container);
            }
        }
    }

    waitForLibs(function() {
        initContainerNodes();
        initializeCollapsedContainersFromGraph();
        reloadGraph();
    });

    function findErrorNode(nodes) {
        return nodes.find(node => adj_list[node].failed);
    }



    // Add this function to render the raw Graphviz visualization
    function renderGraphvizImage(dotSource) {
        // Create a container for the Graphviz image
        const graphvizContainer = d3.select("#graph-container")
            .append("div")
            .attr("id", "graphviz-image-container")
            .style("margin-top", "20px")
            .style("border-top", "1px solid #ccc")
            .style("padding-top", "20px");
        
        graphvizContainer.append("h3")
            .text("Original Graphviz Rendering")
            .style("text-align", "center");
        
        // Use Viz.js to render the DOT source directly to SVG
        svgElement = vizInstance.renderSVGElement(dotSource)
        // Append the SVG element to the container
        graphvizContainer.node().appendChild(svgElement);
            
        // Style the SVG
        d3.select(svgElement)
            .attr("width", "100%")
            .attr("height", "600px")
            .style("background-color", "#f9f9f9");
        
    }

    function createGraph(graphvizData) {
        const svg = d3.select("#graph")
        .append("svg")
        .attr("width", "100%")
        .attr("height", "100%")
        .style("background-color", "#f3f3eb");

        const inner = svg.append("g");

        // Add zoom behavior
        const zoom = d3.zoom().on("zoom", function(event) {
            inner.attr("transform", event.transform);
            closePopup();
        });

        svg.call(zoom);

        // Define arrowhead marker
        svg.append("defs").append("marker")
        .attr("id", "arrowhead")
        .attr("viewBox", "0 -5 10 10")
        .attr("refX", 8)
        .attr("refY", 0)
        .attr("markerWidth", 6)
        .attr("markerHeight", 6)
        .attr("orient", "auto")
        .append("path")
        .attr("d", "M0,-5L10,0L0,5")
        .attr("fill", "#333");

        // Draw clusters (container boxes) first so they're behind nodes
        const clusters = inner.selectAll(".boundary-box")
        .data(Object.entries(graphvizData.clusters))
        .enter()
        .append("g")
        .attr("class", "boundary-box");

        const containersWithFailedNodes = identifyContainersWithFailedNodes(adj_list, ancestor_map);

        clusters.each(function([clusterName, clusterData]) {
            const group = d3.select(this);

            

            const hasFailedNodes = containersWithFailedNodes.has(clusterName);
            if (hasFailedNodes) {
                group.classed("error", true);
            }

            // Parse bounding box coordinates
            const [x1, y1, x2, y2] = clusterData.bb.split(",").map(Number);
            
            // Create boundary box
            group.append("rect")
            .attr("x", x1)
            .attr("y", y1)
            .attr("width", x2 - x1)
            .attr("height", y2 - y1)
            .attr("fill-opacity", 0.0)
            .attr("stroke", "black")
            .attr("stroke-width", 1)
            .attr("rx", 5)
            .attr("ry", 5);
                
            // Create label for the cluster
            const textGroup = group.append("g")
            .attr("transform", `translate(${x1 + 10}, ${y1 + 3})`);
            
            const text = textGroup.append("text")
            .text(graph_node_name_to_without_suffix[clusterData.label] || clusterData.label)
            .attr("text-anchor", "start")
            .attr("dominant-baseline", "hanging")
            .attr("dy", "-6")
            .attr("font-size", 7)
            .attr("fill", "#333")
            .attr("stroke", "#f3f3eb")
            .attr("stroke-width", "5px")
            .attr("paint-order", "stroke")
            .attr("stroke-linejoin", "round");
                        
            
            // Add collapse/expand button
            group.append("circle")
            .attr("class", "collapse-button")
            .attr("cx", x1 + 10)
            .attr("cy", y1 + 10)
            .attr("r", 6)
            .attr("fill", "#fff")
            .attr("stroke", "#333")
            .attr("stroke-width", 1)
            .attr("cursor", "pointer");
            
            group.append("text")
            .attr("class", "collapse-icon")
            .attr("x", x1 + 10)
            .attr("y", y1 + 10)
            .attr("text-anchor", "middle")
            .attr("dominant-baseline", "middle")
            .attr("font-size", "10px")
            .attr("cursor", "pointer")
            .text("-");
        });

        // Add click event handler for collapse buttons
        clusters.selectAll(".collapse-button, .collapse-icon")
        .on("click", function(event, [clusterName, clusterData]) {
            event.stopPropagation(); // Prevent event from bubbling to SVG
            // Collapse the container
            collapsedContainers.add(clusterName);
            
            // Track this container and operation for centering after reload
            lastModifiedContainer = clusterName;
            lastOperation = "collapse";
            reloadGraph();
        });

        
        // Create edges using the path points from Graphviz
        const edges = inner.selectAll(".edge")
        .data(graphvizData.edges)
        .enter()
        .append("g")
        .attr("class", "edge-group");
        
        // Add the actual path
        edges.append("path")
        .attr("class", "edge")
        .attr("d", function(edge) {
            if (edge.path && edge.path.length > 0) {
                    // Create a proper SVG path using Bézier curves
                let d = "";
                const points = edge.path;
                
                    // Start at the first point
                d += `M${points[0].x},${points[0].y}`;
                
                    // Create cubic Bézier curves
                for (let i = 1; i < points.length - 2; i += 3) {
                    if (i + 2 < points.length) {
                        d += ` C${points[i].x},${points[i].y} ${points[i+1].x},${points[i+1].y} ${points[i+2].x},${points[i+2].y}`;
                    }
                }
                
                    // If we have leftover points, draw a line to the last point
                if (points.length % 3 !== 1) {
                    d += ` L${points[points.length-1].x},${points[points.length-1].y}`;
                }
                
                return d;
            }
            return "";
        })
        .attr("marker-end", "url(#arrowhead)");
        
        // Add dimension labels on edges
        edges.each(function(edge) {
            const edgeGroup = d3.select(this);
            const path = edgeGroup.select("path").node();
            
            if (!path) return;
            
            // Find the source node data
            if (!adj_list[edge.source] && !adj_list_collapsed_nodes[edge.source]) return;
            
            // Find the edge data with matching target
            let sourceNodeData = adj_list_collapsed_nodes[edge.source];
            if (!sourceNodeData) {
                sourceNodeData = adj_list[edge.source];
            }
            const edgeData = sourceNodeData.edges.find(e => 
                e.target === edge.target && 
                (e.edge_data_id === undefined || e.edge_data_id.toString() === edge.edge_data_id)
            );
            if (!edgeData || !edgeData.dims) return;
            
            const pointAtStart = edge.labelPos;
            const angle = 0.0;
            
            // Create a background for the text
            const labelGroup = edgeGroup.append("g")
            .attr("class", "edge-label")
            .attr("transform", `translate(${pointAtStart.x}, ${pointAtStart.y})`);
            
            const text = labelGroup.append("text")
            .attr("text-anchor", "middle")
            .attr("dominant-baseline", "central")
            .style("font-size", "6pt")
            .text(edgeData.dims)
            .attr("transform", `rotate(${angle})`)
            .attr("stroke", "#f3f3eb")
            .attr("stroke-width", "3pt")
            .attr("paint-order", "stroke")
            .attr("stroke-linejoin", "round");

            // Add background rectangle
            const bbox = text.node().getBBox();
            labelGroup.insert("rect", "text")
            .attr("x", bbox.x - 2)
            .attr("y", bbox.y - 2)
            .attr("rx", 4)
            .attr("ry", 4)
            .attr("width", bbox.width + 4)
            .attr("height", bbox.height + 4)
            .attr("fill", "rgb(243, 243, 235) ")
            .attr("stroke", "#79797933")
            .attr("stroke-width", "0.5px")
            .attr("fill-opacity", 1.0);
            // .attr("transform", `rotate(${angle})`);
        });
        

        // Add hover effects to edge paths
        edges.each(function() {
            const edgeGroup = d3.select(this);
            const path = edgeGroup.select("path");
            const labelGroup = edgeGroup.select(".edge-label");
            const labelText = labelGroup.select("text");
            const labelBg = labelGroup.select("rect");
            const originalStrokeWidth = path.attr("stroke-width") || 1;
            const originalFontSize = labelText.style("font-size") || "6pt";
            const originalParent = edgeGroup.node().parentNode;
            const originalPosition = Array.from(originalParent.childNodes).indexOf(edgeGroup.node());
            
            function highlightEdge() {
                // Move to end of parent's children list to appear on top
                originalParent.appendChild(edgeGroup.node());
                
                // Make edge thicker
                path.attr("stroke-width", parseFloat(originalStrokeWidth) * 1.4);
                
                // Make label text larger
                labelText.style("font-size", "7pt");
                    
                // Adjust the background rectangle for the label
                const bbox = labelText.node().getBBox();
                labelBg.attr("x", bbox.x - 2)
                    .attr("y", bbox.y - 2)
                    .attr("width", bbox.width + 4)
                    .attr("height", bbox.height + 4);
            }
            
            function resetEdge() {
                // Restore original position in parent's children list
                const currentIndex = Array.from(originalParent.childNodes).indexOf(edgeGroup.node());
                if (currentIndex !== originalPosition) {
                    // Insert back at original position
                    const referenceNode = originalParent.childNodes[originalPosition] || null;
                    originalParent.insertBefore(edgeGroup.node(), referenceNode);
                }
                
                // Restore original edge thickness
                path.attr("stroke-width", originalStrokeWidth);
                
                // Restore original label text size
                labelText.style("font-size", originalFontSize);
                    
                // Restore original background rectangle size
                const bbox = labelText.node().getBBox();
                labelBg.attr("x", bbox.x - 2)
                    .attr("y", bbox.y - 2)
                    .attr("width", bbox.width + 4)
                    .attr("height", bbox.height + 4);
            }
            
            // Add hover events to the path
            path.on("mouseenter", highlightEdge)
                .on("mouseleave", resetEdge);
            
            // Add hover events to label group (both text and background)
            if (labelGroup.node()) {
                labelText.on("mouseenter", highlightEdge)
                    .on("mouseleave", resetEdge);
                    
                labelBg.on("mouseenter", highlightEdge)
                    .on("mouseleave", resetEdge);
            }
        });


        // Create nodes using positions from Graphviz
        const nodes = inner.selectAll(".node")
            .data(Object.entries(graphvizData.nodes))
            .enter()
            .append("g")
            // In the createGraph function, when you're creating nodes, modify the class assignment:
            .attr("class", function([nodeName, nodeData]) {
                let classes = "node";
                if ((adj_list[nodeName] && adj_list[nodeName].failed) || containersWithFailedNodes.has(nodeName)) classes += " error";
                if ((adj_list[nodeName] && adj_list[nodeName].node_type === "Input")) classes += " input";
                else if ((adj_list[nodeName] && adj_list[nodeName].node_type === "Module") || (collapsedContainers.has(nodeName))) classes += " module";
                else if ((adj_list[nodeName] && adj_list[nodeName].node_type === "Output")) classes += " output";
                else if ((adj_list[nodeName] && adj_list[nodeName].node_type === "Operation")) classes += " operation";
                else if ((adj_list[nodeName] && adj_list[nodeName].node_type === "Constant")) classes += " constant";
                return classes;
            })
            .attr("transform", function([nodeName, nodeData]) {
                return `translate(${nodeData.position.x}, ${nodeData.position.y})`;
            });
        
        // Filter nodes for click event - don't include input or output nodes
        inner.selectAll(".node")
            .filter(function([nodeName]) {
                return !(((adj_list[nodeName] && adj_list[nodeName].node_type === "Output")) || ((adj_list[nodeName] && adj_list[nodeName].node_type === "Input")) || ((adj_list[nodeName] && adj_list[nodeName].node_type === "Constant")))
            })
            .on("click", showPopup);

        
        // Add rectangles for nodes
        nodes.append("rect")
        .attr("width", function([nodeName, nodeData]) { return nodeData.width; })
        .attr("height", function([nodeName, nodeData]) { return nodeData.height; })
        .attr("x", function([nodeName, nodeData]) { return -nodeData.width / 2; })
        .attr("y", function([nodeName, nodeData]) { return -nodeData.height / 2; })
        .attr("class", function([nodeName]) { 
            if (adj_list[nodeName] && adj_list[nodeName].node_type === "Input") return "input";
            if (adj_list[nodeName] && adj_list[nodeName].node_type === "Output") return "output";
            if (adj_list[nodeName] && adj_list[nodeName].node_type === "Constant") return "constant";
            if ((adj_list[nodeName] && adj_list[nodeName].failed) || containersWithFailedNodes.has(nodeName)) return "failed";
            return ((adj_list[nodeName] && adj_list[nodeName].node_type === "Module") || (collapsedContainers.has(nodeName))) ? "module" : "operation";
        });
        
        // Add labels for nodes
        nodes.append("text")
        .attr("class", "node-label")
        .text(function([nodeName]) { return graph_node_name_to_without_suffix[nodeName] || nodeName; })
        .attr("text-anchor", "middle")
        .attr("dominant-baseline", "central");
        
        // Add type indicators
        nodes.filter(function([nodeName]) {
            return !(((adj_list[nodeName] && adj_list[nodeName].node_type === "Output")) || ((adj_list[nodeName] && adj_list[nodeName].node_type === "Input")) || ((adj_list[nodeName] && adj_list[nodeName].node_type === "Constant")));
        }).append("text")
        .attr("class", "type-indicator")
        .attr("text-anchor", "end")
        .attr("dominant-baseline", "hanging")
        .attr("transform", function([nodeName, nodeData]) {
            return `translate(${nodeData.width / 2 - 3}, ${-nodeData.height / 2 + 3})`;
        })
        .text(function([nodeName]) {
            return (adj_list[nodeName] && adj_list[nodeName].node_type === "Module") || (collapsedContainers.has(nodeName)) ? "Module" : "Tensor Op";
        });

        // Add collapse/expand buttons to collapsed container nodes
        inner.selectAll(".node")
        .filter(function([nodeName]) {
            return collapsedContainers.has(nodeName);
        })
        .each(function([nodeName, nodeData]) {
            const nodeGroup = d3.select(this);
            const width = nodeData.width;
            const height = nodeData.height;        
            // Add collapse/expand button (with "+" since it's collapsed)
            nodeGroup.append("circle")
            .attr("class", "collapse-button")
            .attr("cx", -width/2 + 8)
            .attr("cy", -height/2 + 8)
            .attr("r", 6)
            .attr("fill", "#fff")
            .attr("stroke", "#333")
            .attr("stroke-width", 1)
            .attr("cursor", "pointer");
        
            nodeGroup.append("text")
            .attr("class", "collapse-icon")
            .attr("x", -width/2 + 8)
            .attr("y", -height/2 + 8) 
            .attr("text-anchor", "middle")
            .attr("dominant-baseline", "middle")
            .attr("font-size", "10px")
            .attr("cursor", "pointer")
            .text("+");
            
            // Add click handler using the existing reloadGraph function
            nodeGroup.selectAll(".collapse-button, .collapse-icon")
            .on("click", function(event) {
                event.stopPropagation();
                
                // Always expanding when clicked (since these are collapsed containers)
                collapsedContainers.delete(nodeName);

                // Track this container and operation for centering after reload
                lastModifiedContainer = nodeName;
                lastOperation = "expand";
                
                // Use the existing reloadGraph function
                reloadGraph();
            });
        });    

        // Add hover effects
        nodes.each(function() {
            const nodeGroup = d3.select(this);
            const rect = nodeGroup.select("rect");
            const originalColor = rect.style("fill");
            let isHovered = false;
            
            nodeGroup.on("mouseenter", function() {
                if (!isHovered) {
                    isHovered = true;
                    const currentColor = d3.color(rect.style("fill") || originalColor);
                    rect.style("fill", currentColor.brighter(0.4));
                }
            })
            .on("mouseleave", function() {
                isHovered = false;
                rect.style("fill", originalColor);
            });
        });

        function createJsonViewer(container, data) {
            // Clear previous contents
            container.innerHTML = '';

            // Create a new div to hold the editor
            const editorContainer = document.createElement('div');
            editorContainer.style.height = '25vh';
            container.appendChild(editorContainer);

            const options = {
                mode: 'code',
                mainMenuBar: false,
                navigationBar: false,
                statusBar: false,
                indentation: 2,
                onChange: function() {
                    editor.set(data);
                }
            };
            const editor = new JSONEditor(editorContainer, options);
            editor.set(data);
            // set a reference for later use during resizing
            container.jsonEditor = editor;
        }

        function showPopup(event, [nodeName]) {
            event.stopPropagation();
            
            const clickedNodeElement = d3.select(this);

            // Position the clicked node
            const clickednodeData = graphvizData.nodes[nodeName];
            if (clickednodeData) {
                const svgNode = svg.node();
                const width = svgNode.clientWidth || 800;
                const height = svgNode.clientHeight || 600;
                
                const currentTransform = d3.zoomTransform(svg.node());
                const targetX = 0.4 * width - clickednodeData.position.x * currentTransform.k;
                const targetY = 0.2 * height - clickednodeData.position.y * currentTransform.k;
                
                svg.call(zoom.transform, d3.zoomIdentity
                    .translate(targetX, targetY)
                    .scale(currentTransform.k));
            }

            clickedNodeElement.classed("highlight-node", true);
            
            setTimeout(() => {
                clickedNodeElement.classed("highlight-node", false);
            }, 2000); // Match the animation duration


            const popup = d3.select("#popup");
            const clickedElement = d3.select(this).select("rect").node();
            const moduleTemplate = d3.select("#module-template");
            const tensorOpTemplate = d3.select("#tensor-op-template");
            const noInfoMessage = d3.select("#no-info");
            
            const nodeData = adj_list[nodeName];
            
            let popupTitle = nodeName;
            if (node_to_module_path[nodeName] && graph_node_name_to_without_suffix[nodeName]) {
                popupTitle = node_to_module_path[nodeName] + "." + graph_node_name_to_without_suffix[nodeName];
            }
            d3.select("#popup-title").text(popupTitle);

            moduleTemplate.style("display", "none");
            tensorOpTemplate.style("display", "none");
            noInfoMessage.style("display", "none");
            
            if (module_info[nodeName]) {
                const info = module_info[nodeName];
                moduleTemplate.style("display", "block").classed("content-shown", true);
                
                d3.select("#module-extra-repr").text(info.extra_repr || 'N/A');

                const forward_info = func_info[nodeName];
                const attributesContainer = d3.select("#module-attributes").node();
                attributesContainer.innerHTML = '';
                createJsonViewer(attributesContainer, info.attributes);

                const parametersContainer = d3.select("#module-parameters").node();
                parametersContainer.innerHTML = '';
                createJsonViewer(parametersContainer, forward_info);
            } else if (!(adj_list[nodeName] && adj_list[nodeName].node_type === "Input") && func_info[nodeName]) {
                const info = func_info[nodeName];
                tensorOpTemplate.style("display", "block").classed("content-shown", true);
                const parametersContainer = d3.select("#tensor-op-parameters").node();
                parametersContainer.innerHTML = '';
                createJsonViewer(parametersContainer, info);
            } else {
                noInfoMessage.style("display", "block").classed("content-shown", true);
            }
            
            const boundingBox = clickedElement.getBoundingClientRect();
            const containerRect = d3.select("#graph-container").node().getBoundingClientRect();
            
            const x = boundingBox.left + boundingBox.width / 2 - containerRect.left;
            const y = boundingBox.top + boundingBox.height / 2 - containerRect.top;
            
            popup.style("left", `${x}px`)
            .style("top", `${y}px`)
            .style("display", "flex");
            d3.select("#popup-content").node().scrollTop = 0;
        }
        
        function closePopup() {
            d3.select("#popup").style("display", "none");
        }
        
        d3.select(".popup-close").on("click", closePopup);
        svg.on("click", closePopup);

        const popup = document.getElementById(`popup`);
        const resizeObserver = new ResizeObserver(() => {
            const editorContainers = popup.querySelectorAll('.json-viewer');
            for (let container of editorContainers) {
                if (container.jsonEditor) {
                    container.jsonEditor.resize();
                }
            }
        });
        resizeObserver.observe(popup);
        
        // Calculate view box to ensure the graph is properly scaled and centered
        const findErrorNode = (nodes) => {
            for (const [nodeName] of nodes) {
                if (adj_list[nodeName] && adj_list[nodeName].failed) {
                    return nodeName;
                }
            }
            return null;
        };
        
        const errorNode = findErrorNode(Object.entries(graphvizData.nodes));
        
        // Initial zoom setup
        const svgNode = svg.node();
        const width = svgNode.clientWidth || 800; // Fallback if clientWidth is 0
        const height = svgNode.clientHeight || 600; // Fallback if clientHeight is 0
        
        if (lastModifiedContainer && (Object.keys(graphvizData.nodes).includes(lastModifiedContainer) || 
            Object.keys(graphvizData.clusters).includes(lastModifiedContainer))) {
            
            // Center on the container that was just expanded or collapsed
            let targetCoverage;
            if (lastOperation === "expand") {
                const nodeCount = containerNodeCounts[lastModifiedContainer] || 1;

                const minCoverage = 0.3;
                const maxNodes = 20;
                const scaleFactor = Math.min(nodeCount, maxNodes) / maxNodes;
                targetCoverage = Math.min(EXPAND_TARGET_COVERAGE, minCoverage + (EXPAND_TARGET_COVERAGE - minCoverage) * scaleFactor);
            } else {
                targetCoverage = COLLAPSE_TARGET_COVERAGE;
            }
            let targetNode;
            let targetPos;
            let containerWidth, containerHeight;
            
            if (Object.keys(graphvizData.nodes).includes(lastModifiedContainer)) {
                // It's a node (collapsed container)
                targetNode = graphvizData.nodes[lastModifiedContainer];
                targetPos = { x: targetNode.position.x, y: targetNode.position.y };
                containerWidth = targetNode.width;
                containerHeight = targetNode.height;
            } else if (Object.keys(graphvizData.clusters).includes(lastModifiedContainer)) {
                // It's a cluster (expanded container)
                const clusterData = graphvizData.clusters[lastModifiedContainer];
                const [x1, y1, x2, y2] = clusterData.bb.split(",").map(Number);
                targetPos = { x: (x1 + x2) / 2, y: (y1 + y2) / 2 };
                containerWidth = x2 - x1;
                containerHeight = y2 - y1;
            }
            
            if (targetPos && containerWidth && containerHeight) {
                // Calculate scale to make container occupy the target percentage of viewport
                const widthScale = (width * targetCoverage) / containerWidth;
                const heightScale = (height * targetCoverage) / containerHeight;
                // Use the smaller scale to ensure container fits in viewport
                const targetScale = Math.min(widthScale, heightScale);
                
                const translate = [
                    width / 2 - targetPos.x * targetScale,
                    height / 2 - targetPos.y * targetScale
                ];
                
                svg.call(zoom.transform, d3.zoomIdentity
                    .translate(translate[0], translate[1])
                    .scale(targetScale));
                
                // Apply highlight effect to the target container
                if (Object.keys(graphvizData.nodes).includes(lastModifiedContainer)) {
                    // Highlight the node (collapsed container)
                    const targetElement = inner.selectAll(".node")
                        .filter(([nodeName]) => nodeName === lastModifiedContainer);
                        
                    if (!targetElement.empty()) {
                        targetElement.classed("highlight-node", true);
                        
                        // Remove the highlight class after animation completes
                        setTimeout(() => {
                            targetElement.classed("highlight-node", false);
                        }, 2000); // Match the animation duration
                    }
                } else if (Object.keys(graphvizData.clusters).includes(lastModifiedContainer)) {
                    // Highlight the cluster (expanded container)
                    const targetElement = inner.selectAll(".boundary-box")
                        .filter(function([clusterName]) {
                            return clusterName === lastModifiedContainer;
                        });
                        
                    if (!targetElement.empty()) {
                        targetElement.classed("highlight-cluster", true);
                        
                        // Remove the highlight class after animation completes
                        setTimeout(() => {
                            targetElement.classed("highlight-cluster", false);
                        }, 2000); // Match the animation duration
                    }
                }
                
                // Reset tracking variables after applying the transformation
                lastModifiedContainer = null;
                lastOperation = null;
            }
        } else if (errorNode) {
            // Focus on the error node if there is one
            const nodeData = graphvizData.nodes[errorNode];
            if (nodeData) {
                const scale = 2; // Zoom in on error node
                const translate = [
                    width / 2 - nodeData.position.x * scale,
                    height / 2 - nodeData.position.y * scale
                    ];
                
                svg.call(zoom.transform, d3.zoomIdentity
                    .translate(translate[0], translate[1])
                    .scale(scale));
            }
        } else {
            // Show the entire graph
            // Find the bounds of the graph
            let minX = Infinity, minY = Infinity;
            let maxX = -Infinity, maxY = -Infinity;
            
            Object.values(graphvizData.nodes).forEach(node => {
                minX = Math.min(minX, node.position.x - node.width / 2);
                minY = Math.min(minY, node.position.y - node.height / 2);
                maxX = Math.max(maxX, node.position.x + node.width / 2);
                maxY = Math.max(maxY, node.position.y + node.height / 2);
            });
            
            // Also consider cluster bounding boxes
            Object.values(graphvizData.clusters).forEach(cluster => {
                const [x1, y1, x2, y2] = cluster.bb.split(",").map(Number);
                minX = Math.min(minX, x1);
                minY = Math.min(minY, y1);
                maxX = Math.max(maxX, x2);
                maxY = Math.max(maxY, y2);
            });
            
            const graphWidth = maxX - minX;
            const graphHeight = maxY - minY;
            
            // Determine scale to fit the entire graph with some padding
            const scale = Math.min(width / graphWidth, height / graphHeight) * 0.9;
            
            // Center the graph
            const translate = [
                width / 2 - (minX + graphWidth / 2) * scale,
                height / 2 - (minY + graphHeight / 2) * scale
                ];
            
            svg.call(zoom.transform, d3.zoomIdentity
                .translate(translate[0], translate[1])
                .scale(scale));
        }
            
    }
})();

            
        </script>
            
        <div class="container">
            <div class="top-bar">
                <h1 class="repo-title">TorchVista</h1>
                <!-- GitHub's official star button -->
                <iframe src="https://ghbtns.com/github-btn.html?user=sachinhosmani&repo=torchvista&type=star&count=true&size=large" 
                        frameborder="0" scrolling="0" width="170" height="30" title="GitHub Star Button"></iframe>
            </div>
            <div class="left-column">

<div class="code-box" id="codeBox">
    <div class="code-box-header">Sample code (Not editable)</div>
    <pre><code class="language-python">${code_contents}</code></pre>
</div>

<div class="error-box" id="errorBox">
    <div class="error-box-header">Error Output (if any)</div>
${error_contents}
</div>

            </div>
    
            <div class="graph-view" id="graphContainer" style="height: 100%">
                <div class="graph-box-header">Visualized Interactive Graph</div>
                <div id="graph-container" style="height: 97%; overflow: auto; position: relative;">
                    <div id="graph" style="height: 100%"></div>
                    <div id="popup" class="popup" style="display: none;">
                        <div class="popup-header">
                            <h3 id="popup-title"></h3>
                            <span class="popup-close">✕</span>
                        </div>
                        <div id="popup-content" class="popup-content">
                            <div id="common-info">
                            </div>
                            <div id="module-template" style="display:none;">
                                <details open id="module-parameters-details">
                                    <summary><strong>forward() Parameters</strong></summary>
                                    <div id="module-parameters" class="json-viewer"></div>
                                </details>
                                <details open id="module-attributes-details">
                                    <summary><strong>Attributes</strong></summary>
                                    <div id="module-attributes" class="json-viewer"></div>
                                </details>
                            </div>
                            <div id="tensor-op-template" style="display:none;">
                                <summary><strong>Parameters</strong></summary>
                                <div id="tensor-op-parameters" class="json-viewer"></div>
                            </div>
                            <p id="no-info" style="display:none;">No further information available for this node.</p>
                        </div>
                
                </div>
            </div>
        </div>
    </body>
</html>
